package se.sll.itintegration.catalogue.capacity.ejb;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;

import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import lombok.extern.slf4j.Slf4j;
import se.sll.itintegration.catalogue.capacity.entity.CapacityEntity;
import se.sll.itintegration.catalogue.capacity.entity.CareUnitReferenceEntity;
import se.sll.itintegration.catalogue.capacity.entity.ProductReferenceEntity;
import se.sll.service.catalogue.common.AbstractJpaFacade;

/**
 *
 * @author khaleddaham
 */
@Slf4j
@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public class CapacityEjb extends AbstractJpaFacade<CareUnitReferenceEntity> {

    @PersistenceContext(unitName = "capacityPU")
    private EntityManager em;

    public CapacityEjb() {
        super(CareUnitReferenceEntity.class);
    }

    @Override
    protected EntityManager getEntityManager() {
        return em;
    }

    @Override
    protected void setEntityManager(EntityManager em) {
        this.em = em;
    }

    /**
     * Creates capacities for given hsaID -> products
     *
     * @param c
     * @return
     */
    public CapacityEntity update(CapacityEntity c) {
        try {
            c.setCreationTime(this.getTimeStamp());
            c.setSourceSystemHsaID("SYSTEM-TEST");
            c.setCreatedByHsaID("SYSTEM-TEST");
            em.persist(c);
            em.flush();
            em.clear();
        } catch (RuntimeException ex) {
            log.error("update failed: ", ex);
            throw ex;
        }
        return c;
    }

    /**
     *
     * @param hsaId
     * @return
     */
    @SuppressWarnings("unchecked")
    public List<CareUnitReferenceEntity> findByHsaId(String hsaId) {
        return em.createQuery("select c from CareUnitReferenceEntity c where c.hsaID = ?1").setParameter(1, hsaId).getResultList();
    }

    /**
     *
     * @param hsaID
     * @param product
     * @param capacity
     * @return
     */
    public CapacityEntity deleteByHsaIDAndProductAndCapacity(String hsaID, String product, String capacity) {
        //TODO : Remove method or change signature?
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    /**
     *
     * @param hsaID
     * @return
     */
    public CareUnitReferenceEntity findCareUnitReference(String hsaID) {
        CareUnitReferenceEntity cre;
        try {
            cre = (CareUnitReferenceEntity) em.createQuery("select c from CareUnitReferenceEntity c where c.hsaID = ?1").setParameter(1, hsaID).getSingleResult();
        } catch (Exception ex) {
            return null;
            //TODO : throw exception?
        }
        return cre;
    }

    /**
     *
     * @param pre
     * @return
     */
    public ProductReferenceEntity update(ProductReferenceEntity pre) {
        try {
            Collection<ProductReferenceEntity> preList = this.findCapacityByProduct(pre);
            if (preList.size() > 0) {
                log.debug("Merging ProductReferenceEntity: " + pre);
                em.merge(pre);
            } else {
                log.debug("Persisting ProductReferenceEntity.");
                em.persist(pre);
                em.flush();
                em.clear();
            }
        } catch (RuntimeException ex) {
            log.error("update failed:", ex);
            throw ex;
        }
        return pre;
    }

    /**
     *
     * @param pre
     * @return
     */
    public ProductReferenceEntity merge(ProductReferenceEntity pre) {
        try {
            log.debug("Merging ProductReferenceEntity: " + pre);
            em.merge(pre);
        } catch (RuntimeException ex) {
            log.error("update failed:", ex);
            throw ex;
        }
        return pre;
    }

    /**
     *
     * @param cre
     * @return
     */
    public CareUnitReferenceEntity update(CareUnitReferenceEntity cre) {
        try {
            Collection<CareUnitReferenceEntity> creList = this.findByHsaId(cre.getHsaID());
            if (creList.size() > 0) {
                log.debug("Merging CareUnitReferenceEntity: " + cre);
                em.merge(cre);
            } else {
                em.persist(cre);
                em.flush();
                em.clear();
            }
        } catch (RuntimeException ex) {
            log.error("update failed:", ex);
            throw ex;
        }
        return cre;
    }

    @SuppressWarnings("unchecked")
    private Collection<ProductReferenceEntity> findCapacityByProduct(ProductReferenceEntity pre) {
        //TODO : Always returns empty Collection
        Collection<ProductReferenceEntity> preList
                = em.createQuery("select p from ProductReferenceEntity p where p.hsaID = ?1").setParameter(1, pre.getHsaID()).getResultList();
        return new ArrayList<>();
    }

    /**
     *
     * @param hsaID
     * @param product
     * @param code
     * @return
     */
    // FIXME: Should use codeSystem as well
    public CareUnitReferenceEntity findCapacity(String hsaID, String product, String code) {
        Query query = em.createQuery("SELECT c FROM CareUnitReferenceEntity c INNER JOIN c.product p INNER JOIN p.capacity cap"
                + " where c.hsaID = ?1 and p.name = ?2 and cap.code = ?3")
                .setParameter(1, hsaID)
                .setParameter(2, product)
                .setParameter(3, code);
        @SuppressWarnings("unchecked")
        List<CareUnitReferenceEntity> resultList = query.getResultList();
        CareUnitReferenceEntity result;
        if (resultList == null || resultList.isEmpty()) {
            result = null;
        } else {
            result = resultList.get(0);
        }
        return result;
    }

    public void mergeCapacity(CapacityEntity e) {
        em.merge(e);
        em.flush();
    }

    /**
     * Gets the units specified by input.
     *
     * @param unitIds A list of HSAID for the requested units. Not
     * <code>null</code>.
     * @return
     */
    public List<CareUnitReferenceEntity> findByUnits(Set<String> unitIds) {
        List<CareUnitReferenceEntity> foundUnits = em.createQuery("SELECT DISTINCT cu FROM CareUnitReferenceEntity cu WHERE cu.hsaID IN :unitList", CareUnitReferenceEntity.class)
                .setParameter("unitList", unitIds).getResultList();

        if (foundUnits.size() != unitIds.size()) {
            log.warn("CapacityEjb.findByUnits(): Found number of units differs from requested number of units");
            log.warn("CapacityEjb.findByUnits(): Requested units: {}", unitIds);
            ArrayList<String> foundUnitIds = new ArrayList<>();
            for (CareUnitReferenceEntity cre : foundUnits) {
                foundUnitIds.add(cre.getHsaID());
            }
            log.warn("CapacityEjb.findByUnits(): Found units: {}", foundUnitIds);
        }

        return foundUnits;
    }

    @Override
    public List<CareUnitReferenceEntity> findAll() {
        return this.em.createQuery("SELECT DISTINCT(c) FROM CareUnitReferenceEntity c JOIN FETCH c.product p JOIN FETCH p.capacity",
                CareUnitReferenceEntity.class).getResultList();
    }
}
